/**
 * Copyright (c) 2011-2017, dafei 李飞 (myaniu AT gmail DOT com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jfinal.ext.plugin.shiro;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresGuest;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.annotation.RequiresUser;

import com.jfinal.config.Routes;
import com.jfinal.config.Routes.Route;
import com.jfinal.core.ActionKey;
import com.jfinal.core.Controller;
import com.jfinal.plugin.IPlugin;

/**
 * Shiro插件，启动时加载所有Shiro访问控制注解。
 * 
 * @author dafei
 *
 */
@SuppressWarnings("unchecked")
public class ShiroPlugin implements IPlugin {

    /**
     * 登录成功时所用的页面。
     */
    private String successUrl = "/";

    /**
     * 登录成功时所用的页面。
     */
    private String loginUrl = "/";

    /**
     * action的扩展名，比如.action或者 .do
     */
    private String extName = "";

    /**
     * 登录成功时所用的页面。
     */
    private String unauthorizedUrl = "/401.jsp";

    private final String SLASH = "/";

    /**
     * Shiro的几种访问控制注解
     */
    private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES = new Class[] {
            RequiresPermissions.class, RequiresRoles.class, RequiresUser.class, RequiresGuest.class,
            RequiresAuthentication.class };

    /**
     * 路由设定
     */
    private static Routes routes=null;
    private static List<Routes> routeList=null;

    /**
     * 构造函数
     * 
     * @param routes
     *            路由设定
     */
    public ShiroPlugin(Routes routes) {
        this.routes = routes;
    }

    public ShiroPlugin(List<Routes> routeList) {
        this.routeList = routeList;
    }
    /**
     * 停止插件
     */
    public boolean stop() {
        return true;
    }

    /**
     * 启动插件
     */
    public boolean start() {
        Set<String> excludedMethodName = buildExcludedMethodName();
        ConcurrentMap<String, AuthzHandler> authzMaps = new ConcurrentHashMap<String, AuthzHandler>();
        // 逐个访问所有注册的Controller，解析Controller及action上的所有Shiro注解。
        // 并依据这些注解，actionKey提前构建好权限检查处理器。
        for (Routes rs : getRoutesList()) {
            for (Route route : rs.getRouteItemList()) {
                Class<? extends Controller> controllerClass = route.getControllerClass();

                String controllerKey = route.getControllerKey();

                // 获取Controller的所有Shiro注解。
                List<Annotation> controllerAnnotations = getAuthzAnnotations(controllerClass);
                // 逐个遍历方法。
                Method[] methods = controllerClass.getMethods();
                for (Method method : methods) {
                    // 排除掉Controller基类的所有方法，并且只关注public方法。
                    if (Modifier.isPublic(method.getModifiers()) && !excludedMethodName.contains(method.getName())) {
                        // 若该方法上存在ClearShiro注解，则对该action不进行访问控制检查。
                        if (isClearShiroAnnotationPresent(method)) {
                            continue;
                        }
                        // 获取方法的所有Shiro注解。
                        List<Annotation> methodAnnotations = getAuthzAnnotations(method);
                        // 依据Controller的注解和方法的注解来生成访问控制处理器。
                        AuthzHandler authzHandler = createAuthzHandler(controllerAnnotations, methodAnnotations);
                        // 生成访问控制处理器成功。
                        if (authzHandler != null) {
                            // 构建ActionKey，参考ActionMapping中实现
                            String actionKey = createActionKey(controllerClass, method, controllerKey);
                            // 添加映射
                            authzMaps.put(actionKey, authzHandler);
                        }
                    }
                }
            }
        }
        // 注入到ShiroKit类中。ShiroKit类以单例模式运行。
        ShiroKit.init(authzMaps);
        /**
         * 设定登录，登录成功，未授权等url地址
         */
        ShiroKit.setLoginUrl(loginUrl);
        ShiroKit.setSuccessUrl(successUrl);
        ShiroKit.setUnauthorizedUrl(unauthorizedUrl);
        ShiroKit.setExtName(extName);
        return true;
    }
    
    /**
     * 获取所有配置的Route
     * @return
     */
//    private List<Routes> getRoutesList() {
//        List<Routes> routesList = Routes.getRoutesList();
//        List<Routes> ret = new ArrayList<Routes>(routesList.size() + 1);
//        ret.add(this.routes);
//        ret.addAll(routesList);
//        return ret;
//    }

    private List<Routes> getRoutesList() {
//        List<Routes> routesList = this.routeList;
        List routesList = Routes.getRoutesList();
        List<Routes> ret = new ArrayList<Routes>(routesList.size());
//        ret.add(this.routes);
//        ret.addAll(routesList);
        ret.addAll(routesList);
        return ret;
    }

    /**
     * 从Controller方法中构建出需要排除的方法列表
     * 
     * @return
     */
    private Set<String> buildExcludedMethodName() {
        Set<String> excludedMethodName = new HashSet<String>();
        Method[] methods = Controller.class.getMethods();
        for (Method m : methods) {
            if(Modifier.isPublic(m.getModifiers())) {
                excludedMethodName.add(m.getName());
            }
        }
        return excludedMethodName;
    }

    /**
     * 依据Controller的注解和方法的注解来生成访问控制处理器。
     * 
     * @param controllerAnnotations
     *            Controller的注解
     * @param methodAnnotations
     *            方法的注解
     * @return 访问控制处理器
     */
    private AuthzHandler createAuthzHandler(List<Annotation> controllerAnnotations,
            List<Annotation> methodAnnotations) {

        // 没有注解
        if (controllerAnnotations.size() == 0 && methodAnnotations.size() == 0) {
            return null;
        }
        // 至少有一个注解
        List<AuthzHandler> authzHandlers = new ArrayList<AuthzHandler>(AUTHZ_ANNOTATION_CLASSES.length);
        for (int index = 0; index < AUTHZ_ANNOTATION_CLASSES.length; index++) {
            authzHandlers.add(null);
        }

        // 逐个扫描注解，若是相应的注解则在相应的位置赋值。
        scanAnnotation(authzHandlers, controllerAnnotations);
        // 逐个扫描注解，若是相应的注解则在相应的位置赋值。函数的注解优先级高于Controller
        scanAnnotation(authzHandlers, methodAnnotations);

        // 去除空值
        List<AuthzHandler> finalAuthzHandlers = new ArrayList<AuthzHandler>();
        for (AuthzHandler a : authzHandlers) {
            if (a != null) {
                finalAuthzHandlers.add(a);
            }
        }
        authzHandlers = null;
        // 存在多个，则构建组合AuthzHandler
        if (finalAuthzHandlers.size() > 1) {
            return new CompositeAuthzHandler(finalAuthzHandlers);
        }
        // 一个的话直接返回
        return finalAuthzHandlers.get(0);
    }

    /**
     * 逐个扫描注解，若是相应的注解则在相应的位置赋值。 注解的处理是有顺序的，依次为RequiresRoles，RequiresPermissions，
     * RequiresAuthentication，RequiresUser，RequiresGuest
     *
     * @param authzArray
     * @param annotations
     */
    private void scanAnnotation(List<AuthzHandler> authzArray, List<Annotation> annotations) {
        if (null == annotations || 0 == annotations.size()) {
            return;
        }
        for (Annotation a : annotations) {
            if (a instanceof RequiresRoles) {
                authzArray.set(0, new RoleAuthzHandler(a));
            } else if (a instanceof RequiresPermissions) {
                authzArray.set(1, new PermissionAuthzHandler(a));
            } else if (a instanceof RequiresAuthentication) {
                authzArray.set(2, AuthenticatedAuthzHandler.me());
            } else if (a instanceof RequiresUser) {
                authzArray.set(3, UserAuthzHandler.me());
            } else if (a instanceof RequiresGuest) {
                authzArray.set(4, GuestAuthzHandler.me());
            }
        }
    }

    /**
     * 构建actionkey，参考ActionMapping中的实现。
     *
     * @param controllerClass
     * @param method
     * @param controllerKey
     * @return
     */
    private String createActionKey(Class<? extends Controller> controllerClass, Method method, String controllerKey) {
        String methodName = method.getName();
        String actionKey = "";

        ActionKey ak = method.getAnnotation(ActionKey.class);
        if (ak != null) {
            actionKey = ak.value().trim();
            if ("".equals(actionKey))
                throw new IllegalArgumentException(controllerClass.getName() + "." + methodName
                        + "(): The argument of ActionKey can not be blank.");
            if (!actionKey.startsWith(SLASH))
                actionKey = SLASH + actionKey;
        } else if (methodName.equals("index")) {
            actionKey = controllerKey;
        } else {
            actionKey = controllerKey.equals(SLASH) ? SLASH + methodName : controllerKey + SLASH + methodName;
        }
        return actionKey;
    }

    /**
     * 返回该方法的所有访问控制注解
     *
     * @param method
     * @return
     */
    private List<Annotation> getAuthzAnnotations(Method method) {
        List<Annotation> annotations = new ArrayList<Annotation>();
        for (Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES) {
            Annotation a = method.getAnnotation(annClass);
            if (a != null) {
                annotations.add(a);
            }
        }
        return annotations;
    }

    /**
     * 返回该Controller的所有访问控制注解
     *
     * @param
     * @return
     */
    private List<Annotation> getAuthzAnnotations(Class<? extends Controller> targetClass) {
        List<Annotation> annotations = new ArrayList<Annotation>();
        for (Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES) {
            Annotation a = targetClass.getAnnotation(annClass);
            if (a != null) {
                annotations.add(a);
            }
        }
        return annotations;
    }

    /**
     * 该方法上是否有ClearShiro注解
     * 
     * @param method
     * @return
     */
    private boolean isClearShiroAnnotationPresent(Method method) {
        Annotation a = method.getAnnotation(ClearShiro.class);
        if (a != null) {
            return true;
        }
        return false;
    }

    /**
     * 登录成功连接
     * 
     * @param successUrl
     */
    public final void setSuccessUrl(String successUrl) {
        this.successUrl = successUrl;
    }

    /**
     * 登录连接
     * 
     * @param loginUrl
     */
    public final void setLoginUrl(String loginUrl) {
        this.loginUrl = loginUrl;
    }

    /**
     * 未授权连接
     * 
     * @param unauthorizedUrl
     */
    public final void setUnauthorizedUrl(String unauthorizedUrl) {
        this.unauthorizedUrl = unauthorizedUrl;
    }

    /**
     * 扩展名，比如.action／.do
     *
     * @param extName
     */
    public final void setExtName(String extName) {
        this.extName = extName;
    }
}
